{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<i>Copyright (c) Microsoft Corporation. All rights reserved.</i>\n",
    "\n",
    "<i>Licensed under the MIT License.</i>\n",
    "\n",
    "# Deployment of a model to Azure App Service and setting CORS policies"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Table of contents\n",
    "\n",
    "1. [Introduction](#intro)\n",
    "1. [Model retrieval](#model)\n",
    "1. [Scoring script](#scoring)\n",
    "1. [Environment retrieval](#environment)\n",
    "1. [Image Build](#image_build)\n",
    "1. [Model deployment as a web application](#deploy)\n",
    "  1. [App service plan](#app_service_plan)\n",
    "  1. [Web application](#web_app)\n",
    "  1. [ACR credentials](#acr_cred)\n",
    "  1. [ACR authentication](#acr_auth)\n",
    "1. [Monitor deployment](#monitor)\n",
    "  1. [Azure Portal](#azure_portal)\n",
    "  1. [Azure CLI](#azure_cli)\n",
    "1. [Test deployment](#test)\n",
    "1. [CORS](#cors)\n",
    "1. [Clean up](#clean_up)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Introduction <a id=\"intro\"/>\n",
    "\n",
    "Azure App Service allows users to build and host web apps, mobile back ends, and RESTful APIs in the programming language of one's choice without managing infrastructure. It offers auto-scaling and high availability with support for both Windows and Linux. It also allows for automated deployments from Github, Azure DevOps, or any Git repo. \n",
    "\n",
    "For our purposes, we will be using its support for hosting RESTful APIs to deploy our trained machine learning model to a RESTful API endpoint.\n",
    "\n",
    "In this tutorial, we will learn how to target Azure Apps as a deployment target using the trained model and resources from our previous notebook [21_deployment_on_azure_container_instances.ipynb](21_deployment_on_azure_container_instances.ipynb). \n",
    "\n",
    "We will also learn how to set Cross-Origin Resource Sharing (CORS) policies that is required when developing a web application hosted in a different domain from the one hosting the deployed machine learning model's RESTful APIs.\n",
    "\n",
    "To learn more about Cross-Origin Resource Sharing (CORS) policies, go to the [CORS section](#CORS) of this notebook."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Pre-requisites <a id=\"pre-reqs\"/>\n",
    "\n",
    "This notebook relies on resources we created in [21_deployment_on_azure_container_instances.ipynb](21_deployment_on_azure_container_instances.ipynb):\n",
    "- Our Azure Machine Learning workspace\n",
    "- Our Azure Machine Learning environment that we registered to the workspace which contains the conda dependencies and the requirements for building the docker image that we used for deploying the model to Azure Container Instances.\n",
    "- The model and scoring script needed for the web service to work.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "If you are missing any of these, you should go back and run the steps from the sections \"Pre-requisites\" to \"3.D Environment setup\" to generate them.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Library import\n",
    "\n",
    "Let's first import the libraries that we will need for this notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's first import required modules for this notebook.\n",
    "import json\n",
    "import requests\n",
    "import sys\n",
    "# This \"sys.path.extend()\" statement allows us to move up the directory hierarchy \n",
    "# and access the Computer Vision Repository utils_cv package\n",
    "sys.path.extend([\".\", \"../..\"])\n",
    "\n",
    "# Import AzureML modules that are required for this notebook\n",
    "from azureml.core import Workspace, Environment\n",
    "from azureml.core.model import Model, InferenceConfig\n",
    "\n",
    "# Import custom vision utilities for retrieving workspaces and for testing\n",
    "from utils_cv.common.azureml import get_or_create_workspace\n",
    "from utils_cv.common.data import data_path\n",
    "from utils_cv.common.image import ims2strlist"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For us to deploy our model to Azure App Services, we first need to retrieve the Azure Machine Learning workspace that we created in previous notebooks (or create one if we have not created one yet). \n",
    "\n",
    "You will need the following information to create or retrieve an AzureML workspace:\n",
    "- subscription_id: The ID of the Azure subscription you wish to use for the workspace.\n",
    "- resource_group: The name of the resource group to use for the workspace.\n",
    "- workspace_name: The name you wish to use for this workspace.\n",
    "- workspace_region: The geographical area to use for this workspace (eastus, eastus2, etc)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "subscription_id = \"YOUR_SUBSCRIPTION_ID\"\n",
    "resource_group = \"YOUR_RESOURCE_GROUP_NAME\"\n",
    "workspace_name = \"YOUR_WORKSPACE_NAME\"\n",
    "workspace_region = \"YOUR_WORKSPACE_REGION\" #Possible values eastus, eastus2 and so on."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you have not created an Azure Machine Learning workspace yet and have specific questions around workspaces, please refer to [AzureML's official documentation page](https://docs.microsoft.com/en-us/python/api/overview/azure/ml/?view=azure-ml-py#workspace)\n",
    "\n",
    "An AzureML workspace contains the various resources that are required across Azure Machine Learning Services, from training to inferencing and is required for this notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Workspace name: cvws\n",
      "Workspace region: eastus\n",
      "Subscription ID: 0ca618d2-22a8-413a-96d0-0f1b531129c3\n",
      "Resource Group: cvbp_project_resources\n"
     ]
    }
   ],
   "source": [
    "ws = get_or_create_workspace(\n",
    "                subscription_id,\n",
    "                resource_group,\n",
    "                workspace_name,\n",
    "                workspace_region)\n",
    "\n",
    "# Let's print the workspace details\n",
    "print(\"Workspace name: \" + ws.name,\n",
    "      \"Workspace region: \" + ws.location,\n",
    "      \"Subscription ID: \" + ws.subscription_id,\n",
    "      \"Resource Group: \" + ws.resource_group, sep = \"\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Retrieve registered model<a id=\"model\" />\n",
    "\n",
    "We will now retrieve the model that we registered to the workspace using the name that we gave `im_classif_resnet18` in [21_deployment_on_azure_container_instances.ipynb](21_deployment_on_azure_container_instances.ipynb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = Model(ws, \"im_classif_resnet18\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Scoring script <a id=\"scoring\" />\n",
    "\n",
    "We will reuse the scoring script that we created in [21_deployment_on_azure_container_instances.ipynb](21_deployment_on_azure_container_instances.ipynb).\n",
    "\n",
    "If you have created a scoring script from the linked notebook, you can skip to the next section: [Environment retrieval](#environment)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### (optional, only required if you have not created one yet)\n",
    "\n",
    "For the web service to return predictions on a given input image, we need to provide it with instructions on how to use the model we just registered. These instructions are stored in the scoring script.\n",
    "\n",
    "This script must contain two required functions, init() and run(input_data):\n",
    "\n",
    "- In the init() function, we typically load the model into a global object. This function is executed only once when the Docker container is started.\n",
    "- In the run(input_data) function, the model is used to predict a value based on the input data. The input and output of run typically use JSON as serialization and de-serialization format but we are not limited to that.\n",
    "\n",
    "This file must also be stored in the current directory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "scoring_script = \"score.py\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting score.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile $scoring_script\n",
    "# Copyright (c) Microsoft. All rights reserved.\n",
    "# Licensed under the MIT license.\n",
    "\n",
    "import os\n",
    "import json\n",
    "\n",
    "from base64 import b64decode\n",
    "from io import BytesIO\n",
    "\n",
    "from azureml.core.model import Model\n",
    "from fastai.vision import load_learner, open_image\n",
    "\n",
    "def init():\n",
    "    global model\n",
    "    model_path = Model.get_model_path(model_name='im_classif_resnet18')\n",
    "    # ! We cannot use the *model_name* variable here otherwise the execution on Azure will fail !\n",
    "\n",
    "    model_dir_path, model_filename = os.path.split(model_path)\n",
    "    model = load_learner(model_dir_path, model_filename)\n",
    "\n",
    "\n",
    "def run(raw_data):\n",
    "\n",
    "    # Expects raw_data to be a list within a json file\n",
    "    result = []    \n",
    "    \n",
    "    for im_string in json.loads(raw_data)['data']:\n",
    "        im_bytes = b64decode(im_string)\n",
    "        try:\n",
    "            im = open_image(BytesIO(im_bytes))\n",
    "            pred_class, pred_idx, outputs = model.predict(im)\n",
    "            result.append({\"label\": str(pred_class), \"probability\": str(outputs[pred_idx].item())})\n",
    "        except Exception as e:\n",
    "            result.append({\"label\": str(e), \"probability\": ''})\n",
    "    return result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Retrieve registered environment<a id=\"environment\" />\n",
    "\n",
    "We will retrieve the environment that we registered to the workspace for reuse in our deployment to Azure App Services.\n",
    "\n",
    "The retrieved environment will contain the conda packages and dependencies that we specified in [21_deployment_on_azure_container_instances.ipynb](21_deployment_on_azure_container_instances.ipynb). \n",
    "\n",
    "It will also contain the docker specifications such as the base dockerfile to use to build the image and the required inferencing packages that are needed in the resulting docker image for the deployment to work.\n",
    "\n",
    "NOTE: As mentioned before, we need to create a conda environment that contains the required dependencies for the trained model. Normally, this means that we need to create a conda environment that closely matches the one in which the model was trained.\n",
    "\n",
    "For additional details on using environments, please refer to [AzureML's documentation page on environments](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-use-environments)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "inferencing_env = Environment.get(workspace=ws, name=\"im_classif_resnet18\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Build the image used for deployment<a id=\"image_build\" />\n",
    "\n",
    "Let's now create an `InferenceConfig` using the environment we just retrieved and the scoring script.\n",
    "\n",
    "We will then package up the model, the scoring script and the environment (containing the conda dependencies that we specified) to build the final image that will be used for deployment.\n",
    "\n",
    "The built image will be pushed into the Azure Container Registry (ACR) for the workspace, which we will be using in later steps.\n",
    "\n",
    "We can monitor the image build process through the streamed logs.\n",
    "\n",
    "NOTE: It may take a minute or two for the docker image build logs to start streaming."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2020/04/10 11:33:12 Downloading source code...\n",
      "2020/04/10 11:33:14 Finished downloading source code\n",
      "2020/04/10 11:33:14 Creating Docker network: acb_default_network, driver: 'bridge'\n",
      "2020/04/10 11:33:15 Successfully set up Docker network: acb_default_network\n",
      "2020/04/10 11:33:15 Setting up Docker configuration...\n",
      "2020/04/10 11:33:16 Successfully set up Docker configuration\n",
      "2020/04/10 11:33:16 Logging in to registry: cvwsbbd59dea.azurecr.io\n",
      "2020/04/10 11:33:17 Successfully logged into cvwsbbd59dea.azurecr.io\n",
      "2020/04/10 11:33:17 Executing step ID: acb_step_0. Timeout(sec): 5400, Working directory: '', Network: 'acb_default_network'\n",
      "2020/04/10 11:33:17 Launching container with name: acb_step_0\n",
      "WARNING! Using --password via the CLI is insecure. Use --password-stdin.\n",
      "WARNING! Your password will be stored unencrypted in /acb/home/.docker/config.json.\n",
      "Configure a credential helper to remove this warning. See\n",
      "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\n",
      "\n",
      "Login Succeeded\n",
      "2020/04/10 11:33:18 Successfully executed container: acb_step_0\n",
      "2020/04/10 11:33:18 Executing step ID: acb_step_1. Timeout(sec): 5400, Working directory: '', Network: 'acb_default_network'\n",
      "2020/04/10 11:33:18 Scanning for dependencies...\n",
      "2020/04/10 11:33:19 Successfully scanned dependencies\n",
      "2020/04/10 11:33:19 Launching container with name: acb_step_1\n",
      "Sending build context to Docker daemon  61.95kB\n",
      "Step 1/8 : FROM cvwsbbd59dea.azurecr.io/azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "latest: Pulling from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "a1298f4ce990: Already exists\n",
      "04a3282d9c4b: Already exists\n",
      "9b0d3db6dc03: Already exists\n",
      "8269c605f3f1: Already exists\n",
      "6504d449e70c: Already exists\n",
      "4e38f320d0d4: Already exists\n",
      "b0a763e8ee03: Already exists\n",
      "11917a028ca4: Already exists\n",
      "a6c378d11cbf: Already exists\n",
      "6cc007ad9140: Already exists\n",
      "6c1698a608f3: Already exists\n",
      "7458244ecb9a: Pulling fs layer\n",
      "9d5bef33743e: Pulling fs layer\n",
      "f29bd095d49d: Pulling fs layer\n",
      "2e65660ae74d: Pulling fs layer\n",
      "a69e08311190: Pulling fs layer\n",
      "e9700fc41de1: Pulling fs layer\n",
      "0ceb2ba7a448: Pulling fs layer\n",
      "6d918a7e9190: Pulling fs layer\n",
      "eb70f4c07ff8: Pulling fs layer\n",
      "946b34be2345: Pulling fs layer\n",
      "2e65660ae74d: Waiting\n",
      "6d918a7e9190: Waiting\n",
      "eb70f4c07ff8: Waiting\n",
      "946b34be2345: Waiting\n",
      "a69e08311190: Waiting\n",
      "e9700fc41de1: Waiting\n",
      "0ceb2ba7a448: Waiting\n",
      "f29bd095d49d: Verifying Checksum\n",
      "f29bd095d49d: Download complete\n",
      "9d5bef33743e: Verifying Checksum\n",
      "9d5bef33743e: Download complete\n",
      "a69e08311190: Verifying Checksum\n",
      "a69e08311190: Download complete\n",
      "e9700fc41de1: Verifying Checksum\n",
      "e9700fc41de1: Download complete\n",
      "0ceb2ba7a448: Verifying Checksum\n",
      "0ceb2ba7a448: Download complete\n",
      "6d918a7e9190: Verifying Checksum\n",
      "6d918a7e9190: Download complete\n",
      "2e65660ae74d: Verifying Checksum\n",
      "2e65660ae74d: Download complete\n",
      "946b34be2345: Verifying Checksum\n",
      "946b34be2345: Download complete\n",
      "7458244ecb9a: Verifying Checksum\n",
      "7458244ecb9a: Download complete\n",
      "7458244ecb9a: Pull complete\n",
      "9d5bef33743e: Pull complete\n",
      "f29bd095d49d: Pull complete\n",
      "2e65660ae74d: Pull complete\n",
      "a69e08311190: Pull complete\n",
      "e9700fc41de1: Pull complete\n",
      "0ceb2ba7a448: Pull complete\n",
      "6d918a7e9190: Pull complete\n",
      "\n",
      "eb70f4c07ff8: Verifying Checksum\n",
      "eb70f4c07ff8: Download complete\n",
      "eb70f4c07ff8: Pull complete\n",
      "946b34be2345: Pull complete\n",
      "Digest: sha256:f9f43c62c1a99f949d80c18af70fdd175cebc8a0994cc0d3a8f505612ca3a01b\n",
      "Status: Downloaded newer image for cvwsbbd59dea.azurecr.io/azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11:latest\n",
      " ---> 9391fb3a6709\n",
      "Step 2/8 : ENV AZUREML_MODEL_DIR=azureml-models/im_classif_resnet18/11\n",
      " ---> Running in a800da7921e2\n",
      "Removing intermediate container a800da7921e2\n",
      " ---> faa618c3a068\n",
      "Step 3/8 : RUN mkdir -p '/var/azureml-app' && /var/azureml-util/download_asset.sh 'https://cvws3283626843.blob.core.windows.net/azureml/LocalUpload/6f88d2e0/tmpsj7sj0t4.py?sv=2019-02-02&sr=b&sig=xXIrekAhcNe%2FCCWNO8pAHkBYrdm%2FYfRtKLKkJNMtb3w%3D&st=2020-04-10T11%3A22%3A47Z&se=2020-04-10T19%3A32%3A47Z&sp=r' '/var/azureml-app/tmpsj7sj0t4.py'\n",
      " ---> Running in eb818f66cc34\n",
      "Downloading https://cvws3283626843.blob.core.windows.net/azureml/LocalUpload/6f88d2e0/tmpsj7sj0t4.py?sv=2019-02-02&sr=b&sig=xXIrekAhcNe%2FCCWNO8pAHkBYrdm%2FYfRtKLKkJNMtb3w%3D&st=2020-04-10T11%3A22%3A47Z&se=2020-04-10T19%3A32%3A47Z&sp=r as /var/azureml-app/tmpsj7sj0t4.py\n",
      "\u001b[91mwget: /azureml-envs/azureml_53f7459b2d11a780edbefaa1f46263fa/lib/libcrypto.so.1.0.0: no version information available (required by wget)\n",
      "wget: /azureml-envs/azureml_53f7459b2d11a780edbefaa1f46263fa/lib/libssl.so.1.0.0: no version information available (required by wget)\n",
      "wget: /azureml-envs/azureml_53f7459b2d11a780edbefaa1f46263fa/lib/libssl.so.1.0.0: no version information available (required by wget)\n",
      "\u001b[0m\u001b[91m--2020-04-10 11:35:04--  https://cvws3283626843.blob.core.windows.net/azureml/LocalUpload/6f88d2e0/tmpsj7sj0t4.py?sv=2019-02-02&sr=b&sig=xXIrekAhcNe%2FCCWNO8pAHkBYrdm%2FYfRtKLKkJNMtb3w%3D&st=2020-04-10T11%3A22%3A47Z&se=2020-04-10T19%3A32%3A47Z&sp=r\n",
      "\u001b[0m\u001b[91mResolving cvws3283626843.blob.core.windows.net (cvws3283626843.blob.core.windows.net)... \u001b[0m\u001b[91m52.239.152.138\n",
      "Connecting to cvws3283626843.blob.core.windows.net (cvws3283626843.blob.core.windows.net)|52.239.152.138|:443... \u001b[0m\u001b[91mconnected.\n",
      "\u001b[0m\u001b[91mHTTP request sent, awaiting response... \u001b[0m\u001b[91m200 OK\n",
      "Length: 1476 (1.4K) [application/octet-stream]\n",
      "Saving to: ‘/var/azureml-app/tmpsj7sj0t4.py’\n",
      "\u001b[0m\u001b[91m\n",
      "     0K                                    100%  180M=0s\n",
      "\n",
      "2020-04-10 11:35:04 (180 MB/s) - ‘/var/azureml-app/tmpsj7sj0t4.py’ saved [1476/1476]\n",
      "\n",
      "\u001b[0mRemoving intermediate container eb818f66cc34\n",
      " ---> 1452f75d1b23\n",
      "Step 4/8 : RUN mkdir -p '/var/azureml-app' && /var/azureml-util/download_asset.sh 'https://cvws3283626843.blob.core.windows.net/azureml/LocalUpload/2c3e41c4/score.py?sv=2019-02-02&sr=b&sig=%2BeTmymL1I8El6y7o0O4GVNwE4c0UiNyncejCQtKbjt0%3D&st=2020-04-10T11%3A22%3A47Z&se=2020-04-10T19%3A32%3A47Z&sp=r' '/var/azureml-app/score.py'\n",
      " ---> Running in 6d0e1e0c80a9\n",
      "Downloading https://cvws3283626843.blob.core.windows.net/azureml/LocalUpload/2c3e41c4/score.py?sv=2019-02-02&sr=b&sig=%2BeTmymL1I8El6y7o0O4GVNwE4c0UiNyncejCQtKbjt0%3D&st=2020-04-10T11%3A22%3A47Z&se=2020-04-10T19%3A32%3A47Z&sp=r as /var/azureml-app/score.py\n",
      "\u001b[91mwget: /azureml-envs/azureml_53f7459b2d11a780edbefaa1f46263fa/lib/libcrypto.so.1.0.0: no version information available (required by wget)\n",
      "wget: /azureml-envs/azureml_53f7459b2d11a780edbefaa1f46263fa/lib/libssl.so.1.0.0: no version information available (required by wget)\n",
      "wget: /azureml-envs/azureml_53f7459b2d11a780edbefaa1f46263fa/lib/libssl.so.1.0.0: no version information available (required by wget)\n",
      "\u001b[0m\u001b[91m--2020-04-10 11:35:08--  https://cvws3283626843.blob.core.windows.net/azureml/LocalUpload/2c3e41c4/score.py?sv=2019-02-02&sr=b&sig=%2BeTmymL1I8El6y7o0O4GVNwE4c0UiNyncejCQtKbjt0%3D&st=2020-04-10T11%3A22%3A47Z&se=2020-04-10T19%3A32%3A47Z&sp=r\n",
      "\u001b[0m\u001b[91mResolving cvws3283626843.blob.core.windows.net (cvws3283626843.blob.core.windows.net)... \u001b[0m\u001b[91m52.239.152.138\n",
      "Connecting to cvws3283626843.blob.core.windows.net (cvws3283626843.blob.core.windows.net)|52.239.152.138|:443... \u001b[0m\u001b[91mconnected.\n",
      "\u001b[0m\u001b[91mHTTP request sent, awaiting response... \u001b[0m\u001b[91m200 OK\n",
      "Length: 1131 (1.1K) [application/octet-stream]\n",
      "Saving to: ‘/var/azureml-app/score.py’\n",
      "\u001b[0m\u001b[91m\n",
      "     0K                                    100%  146M=0s\n",
      "\n",
      "\u001b[0m\u001b[91m2020-04-10 11:35:08 (146 MB/s) - ‘/var/azureml-app/score.py’ saved [1131/1131]\n",
      "\n",
      "\u001b[0mRemoving intermediate container 6d0e1e0c80a9\n",
      " ---> 18d21207000a\n",
      "Step 5/8 : RUN mkdir -p '/var/azureml-app/azureml-models/im_classif_resnet18/11' && /var/azureml-util/download_asset.sh 'https://cvws3283626843.blob.core.windows.net/azureml/ExperimentRun/dcid.9eb442c4-821a-4e0f-9479-ecc33d86b86f/outputs/im_classif_resnet18.pkl?sv=2019-02-02&sr=b&sig=tpGQ5LtW5zAYimv2S7VzSM2T1a0lxo8rNIui73x0ljA%3D&st=2020-04-10T11%3A22%3A47Z&se=2020-04-10T19%3A32%3A47Z&sp=r' '/var/azureml-app/azureml-models/im_classif_resnet18/11/im_classif_resnet18.pkl'\n",
      " ---> Running in 75d9a7061cc1\n",
      "Downloading https://cvws3283626843.blob.core.windows.net/azureml/ExperimentRun/dcid.9eb442c4-821a-4e0f-9479-ecc33d86b86f/outputs/im_classif_resnet18.pkl?sv=2019-02-02&sr=b&sig=tpGQ5LtW5zAYimv2S7VzSM2T1a0lxo8rNIui73x0ljA%3D&st=2020-04-10T11%3A22%3A47Z&se=2020-04-10T19%3A32%3A47Z&sp=r as /var/azureml-app/azureml-models/im_classif_resnet18/11/im_classif_resnet18.pkl\n",
      "\u001b[91mwget: /azureml-envs/azureml_53f7459b2d11a780edbefaa1f46263fa/lib/libcrypto.so.1.0.0: no version information available (required by wget)\n",
      "wget: /azureml-envs/azureml_53f7459b2d11a780edbefaa1f46263fa/lib/libssl.so.1.0.0: no version information available (required by wget)\n",
      "wget: /azureml-envs/azureml_53f7459b2d11a780edbefaa1f46263fa/lib/libssl.so.1.0.0: no version information available (required by wget)\n",
      "\u001b[0m\u001b[91m--2020-04-10 11:35:11--  https://cvws3283626843.blob.core.windows.net/azureml/ExperimentRun/dcid.9eb442c4-821a-4e0f-9479-ecc33d86b86f/outputs/im_classif_resnet18.pkl?sv=2019-02-02&sr=b&sig=tpGQ5LtW5zAYimv2S7VzSM2T1a0lxo8rNIui73x0ljA%3D&st=2020-04-10T11%3A22%3A47Z&se=2020-04-10T19%3A32%3A47Z&sp=r\n",
      "\u001b[0m\u001b[91mResolving cvws3283626843.blob.core.windows.net (cvws3283626843.blob.core.windows.net)... \u001b[0m\u001b[91m52.239.152.138\n",
      "Connecting to cvws3283626843.blob.core.windows.net (cvws3283626843.blob.core.windows.net)|52.239.152.138|:443... \u001b[0m\u001b[91mconnected.\n",
      "\u001b[0m\u001b[91mHTTP request sent, awaiting response... \u001b[0m\u001b[91m200 OK\n",
      "Length: 46881007 (45M) [application/octet-stream]\n",
      "\u001b[0m\u001b[91mSaving to: ‘/var/azureml-app/azureml-models/im_classif_resnet18/11/im_classif_resnet18.pkl’\n",
      "\u001b[0m\u001b[91m\n",
      "     0K .\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m .\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m .\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m .\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m. 71% 15.5M 1s\u001b[0m\u001b[91m\n",
      " 32768K .\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m .\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m                      100% 18.2M=2.8s\n",
      "\n",
      "\u001b[0m\u001b[91m2020-04-10 11:35:14 (16.2 MB/s) - ‘/var/azureml-app/azureml-models/im_classif_resnet18/11/im_classif_resnet18.pkl’ saved [46881007/46881007]\n",
      "\n",
      "\u001b[0mRemoving intermediate container 75d9a7061cc1\n",
      " ---> 725812c82920\n",
      "Step 6/8 : RUN mkdir -p '/var/azureml-app' && echo eyJhY2NvdW50Q29udGV4dCI6eyJzdWJzY3JpcHRpb25JZCI6IjBjYTYxOGQyLTIyYTgtNDEzYS05NmQwLTBmMWI1MzExMjljMyIsInJlc291cmNlR3JvdXBOYW1lIjoiY3ZicF9wcm9qZWN0X3Jlc291cmNlcyIsImFjY291bnROYW1lIjoiY3Z3cyIsIndvcmtzcGFjZUlkIjoiZjlmZDZhMTktZWI4Ny00YmNiLTgzODUtZjk3NjhhODZmNjUxIn0sIm1vZGVscyI6eyJpbV9jbGFzc2lmX3Jlc25ldDE4Ijp7InZlcnNpb24iOjExLCJpZCI6ImltX2NsYXNzaWZfcmVzbmV0MTg6MTEiLCJpbnRlcm5hbElkIjoiZDE1ZmNlZDUyMWJmNDdhZWFjZGUwNGJhOTFmMDRlOTgifX0sIm1vZGVsc0luZm8iOnsiaW1fY2xhc3NpZl9yZXNuZXQxOCI6eyIxMSI6eyJ2ZXJzaW9uIjoxMSwiaWQiOiJpbV9jbGFzc2lmX3Jlc25ldDE4OjExIiwiaW50ZXJuYWxJZCI6ImQxNWZjZWQ1MjFiZjQ3YWVhY2RlMDRiYTkxZjA0ZTk4IiwiZGF0YUNvbGxlY3RvclN0b3JhZ2VQYXRoIjoiL21vZGVsZGF0YS8wY2E2MThkMi0yMmE4LTQxM2EtOTZkMC0wZjFiNTMxMTI5YzMvY3ZicF9wcm9qZWN0X3Jlc291cmNlcy9jdndzL3t3ZWJzZXJ2aWNlX25hbWV9L2ltX2NsYXNzaWZfcmVzbmV0MTgvMTEvIn19fX0= | base64 --decode > /var/azureml-app/model_config_map.json\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " ---> Running in 0fece4105885\n",
      "Removing intermediate container 0fece4105885\n",
      " ---> 77a7a365e29f\n",
      "Step 7/8 : RUN mv '/var/azureml-app/tmpsj7sj0t4.py' /var/azureml-app/main.py\n",
      " ---> Running in 9689a888ad8a\n",
      "Removing intermediate container 9689a888ad8a\n",
      " ---> b4f18a88d2b9\n",
      "Step 8/8 : CMD [\"runsvdir\",\"/var/runit\"]\n",
      " ---> Running in 52040ed3810c\n",
      "Removing intermediate container 52040ed3810c\n",
      " ---> b8cf162127fc\n",
      "Successfully built b8cf162127fc\n",
      "Successfully tagged cvwsbbd59dea.azurecr.io/azureml/azureml_84830b7adc0a892fa927d004f3b1fcb2:latest\n",
      "2020/04/10 11:35:29 Successfully executed container: acb_step_1\n",
      "2020/04/10 11:35:29 Executing step ID: acb_step_2. Timeout(sec): 5400, Working directory: '', Network: 'acb_default_network'\n",
      "2020/04/10 11:35:29 Pushing image: cvwsbbd59dea.azurecr.io/azureml/azureml_84830b7adc0a892fa927d004f3b1fcb2:latest, attempt 1\n",
      "The push refers to repository [cvwsbbd59dea.azurecr.io/azureml/azureml_84830b7adc0a892fa927d004f3b1fcb2]\n",
      "2179889df0f6: Preparing\n",
      "b45f748a732a: Preparing\n",
      "91d596acc7d7: Preparing\n",
      "c4b077207722: Preparing\n",
      "3fa271451871: Preparing\n",
      "1bf21c5cbd6d: Preparing\n",
      "b156ccc9c968: Preparing\n",
      "fa581aa133db: Preparing\n",
      "dcf2f1c2d5fe: Preparing\n",
      "7049b31c7d29: Preparing\n",
      "7ca872f79cb3: Preparing\n",
      "597926253e5a: Preparing\n",
      "a52b426fb3ad: Preparing\n",
      "a49a0ffb7d87: Preparing\n",
      "5351bc49e1c0: Preparing\n",
      "e1171d4d60ca: Preparing\n",
      "6ef1a8ae63b7: Preparing\n",
      "85389f9ead9e: Preparing\n",
      "f2608f66a0e3: Preparing\n",
      "0e259b09e5f4: Preparing\n",
      "340dc32eb998: Preparing\n",
      "df18b66efaa6: Preparing\n",
      "ccdb13a20bf2: Preparing\n",
      "9513cdf4e497: Preparing\n",
      "7f083f9454c0: Preparing\n",
      "29f36b5893dc: Preparing\n",
      "1bf21c5cbd6d: Waiting\n",
      "b156ccc9c968: Waiting\n",
      "fa581aa133db: Waiting\n",
      "dcf2f1c2d5fe: Waiting\n",
      "7049b31c7d29: Waiting\n",
      "7ca872f79cb3: Waiting\n",
      "597926253e5a: Waiting\n",
      "a52b426fb3ad: Waiting\n",
      "a49a0ffb7d87: Waiting\n",
      "5351bc49e1c0: Waiting\n",
      "e1171d4d60ca: Waiting\n",
      "6ef1a8ae63b7: Waiting\n",
      "85389f9ead9e: Waiting\n",
      "f2608f66a0e3: Waiting\n",
      "0e259b09e5f4: Waiting\n",
      "340dc32eb998: Waiting\n",
      "df18b66efaa6: Waiting\n",
      "ccdb13a20bf2: Waiting\n",
      "9513cdf4e497: Waiting\n",
      "7f083f9454c0: Waiting\n",
      "29f36b5893dc: Waiting\n",
      "c4b077207722: Pushed\n",
      "3fa271451871: Pushed\n",
      "91d596acc7d7: Pushed\n",
      "1bf21c5cbd6d: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "b156ccc9c968: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "b45f748a732a: Pushed\n",
      "fa581aa133db: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "2179889df0f6: Pushed\n",
      "dcf2f1c2d5fe: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "7049b31c7d29: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "a52b426fb3ad: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "7ca872f79cb3: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "597926253e5a: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "a49a0ffb7d87: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "5351bc49e1c0: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "85389f9ead9e: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "6ef1a8ae63b7: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "f2608f66a0e3: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "e1171d4d60ca: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "0e259b09e5f4: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "340dc32eb998: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "df18b66efaa6: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "ccdb13a20bf2: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "9513cdf4e497: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "7f083f9454c0: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "29f36b5893dc: Mounted from azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "latest: digest: sha256:4e3b949aea1fd9177ea5f53ecf1a766c9ba5c61e912c4c00b17eb9376ac26708 size: 5767\n",
      "2020/04/10 11:35:44 Successfully pushed image: cvwsbbd59dea.azurecr.io/azureml/azureml_84830b7adc0a892fa927d004f3b1fcb2:latest\n",
      "2020/04/10 11:35:44 Step ID: acb_step_0 marked as successful (elapsed time in seconds: 1.253303)\n",
      "2020/04/10 11:35:44 Step ID: acb_step_1 marked as successful (elapsed time in seconds: 130.621828)\n",
      "2020/04/10 11:35:44 Populating digests for step ID: acb_step_1...\n",
      "2020/04/10 11:35:45 Successfully populated digests for step ID: acb_step_1\n",
      "2020/04/10 11:35:45 Step ID: acb_step_2 marked as successful (elapsed time in seconds: 15.080606)\n",
      "2020/04/10 11:35:45 The following dependencies were found:\n",
      "2020/04/10 11:35:45 \n",
      "- image:\n",
      "    registry: cvwsbbd59dea.azurecr.io\n",
      "    repository: azureml/azureml_84830b7adc0a892fa927d004f3b1fcb2\n",
      "    tag: latest\n",
      "    digest: sha256:4e3b949aea1fd9177ea5f53ecf1a766c9ba5c61e912c4c00b17eb9376ac26708\n",
      "  runtime-dependency:\n",
      "    registry: cvwsbbd59dea.azurecr.io\n",
      "    repository: azureml/azureml_a9f62c4b6fbf408218d5b60ae4f43f11\n",
      "    tag: latest\n",
      "    digest: sha256:f9f43c62c1a99f949d80c18af70fdd175cebc8a0994cc0d3a8f505612ca3a01b\n",
      "  git: {}\n",
      "\n",
      "\n",
      "Run ID: ca4p was successful after 2m34s\n",
      "Package creation Succeeded\n"
     ]
    }
   ],
   "source": [
    "inference_config = InferenceConfig(entry_script=\"score.py\", environment=inferencing_env)\n",
    "\n",
    "inference_image = Model.package(ws, [model], inference_config)\n",
    "# Setting show_output to True to stream the logs from the Docker image build process\n",
    "inference_image.wait_for_creation(show_output=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's print the location of the built image on Azure Container Registry (ACR) so that we can use it to deploy our model to Azure App Services."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "image location on ACR: cvwsbbd59dea.azurecr.io/azureml/azureml_84830b7adc0a892fa927d004f3b1fcb2@sha256:4e3b949aea1fd9177ea5f53ecf1a766c9ba5c61e912c4c00b17eb9376ac26708\n"
     ]
    }
   ],
   "source": [
    "image_location = inference_image.location\n",
    "print(\"image location on ACR: {}\".format(image_location))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Deploy image as a web application<a id=\"deploy\" />\n",
    "\n",
    "These steps require the Azure CLI, so please make sure you have installed it. \n",
    "\n",
    "If you haven't, please install it following the [Azure CLI installation documentation](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's run `az login` to make sure that the Azure CLI is installed and that you're logged into Azure."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "!az login"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.A. Create an app service plan to deploy the service<a id=\"app_service_plan\" />\n",
    "\n",
    "Now that we've retrieved the credentials for the ACR that hosts the image we'll be using to deploy to Azure App Services, we'll now create an app service plan for the deployment.\n",
    "\n",
    "We'll use the P1v2 (`--sku P1v2`) SKU and since images created by Azure Machine Learning Services are linux-based, we need to specify (`--is-linux`) to indicate that we'll be using a linux based image.\n",
    "\n",
    "The full list of other SKUs that are available along with their pricing can be found at [https://azure.microsoft.com/en-us/pricing/details/app-service/linux/](https://azure.microsoft.com/en-us/pricing/details/app-service/linux/)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "appservice_plan_name = \"im_classif_webapp_plan\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You will see the JSON output of the created app service plan once the creation is successful."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"freeOfferExpirationTime\": null,\n",
      "  \"geoRegion\": \"Central US\",\n",
      "  \"hostingEnvironmentProfile\": null,\n",
      "  \"hyperV\": false,\n",
      "  \"id\": \"/subscriptions/0ca618d2-22a8-413a-96d0-0f1b531129c3/resourceGroups/cvbp_project_resources/providers/Microsoft.Web/serverfarms/im_classif_webapp_plan\",\n",
      "  \"isSpot\": false,\n",
      "  \"isXenon\": false,\n",
      "  \"kind\": \"linux\",\n",
      "  \"location\": \"Central US\",\n",
      "  \"maximumElasticWorkerCount\": 1,\n",
      "  \"maximumNumberOfWorkers\": 30,\n",
      "  \"name\": \"im_classif_webapp_plan\",\n",
      "  \"numberOfSites\": 0,\n",
      "  \"perSiteScaling\": false,\n",
      "  \"provisioningState\": \"Succeeded\",\n",
      "  \"reserved\": true,\n",
      "  \"resourceGroup\": \"cvbp_project_resources\",\n",
      "  \"sku\": {\n",
      "    \"capabilities\": null,\n",
      "    \"capacity\": 1,\n",
      "    \"family\": \"Pv2\",\n",
      "    \"locations\": null,\n",
      "    \"name\": \"P1v2\",\n",
      "    \"size\": \"P1v2\",\n",
      "    \"skuCapacity\": null,\n",
      "    \"tier\": \"PremiumV2\"\n",
      "  },\n",
      "  \"spotExpirationTime\": null,\n",
      "  \"status\": \"Ready\",\n",
      "  \"subscription\": \"0ca618d2-22a8-413a-96d0-0f1b531129c3\",\n",
      "  \"tags\": null,\n",
      "  \"targetWorkerCount\": 0,\n",
      "  \"targetWorkerSizeId\": 0,\n",
      "  \"type\": \"Microsoft.Web/serverfarms\",\n",
      "  \"workerTierName\": null\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "!az appservice plan create --resource-group {resource_group} --name {appservice_plan_name} --sku P1v2 --is-linux"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.B. Create and deploy a web app that hosts REST APIs using our trained machine learning model.<a id=\"web_app\" />\n",
    "\n",
    "We will now create and deploy a web app that will host the RESTful APIs for our trained machine learning model using the docker image that we built and pushed to ACR.\n",
    "\n",
    "NOTE: The name used for the web app must be able to produce a unique Fully Qualified Domain Name (FQDN) as AppName.azurewebsites.net.\n",
    "\n",
    "\n",
    "NOTE: The name of your image on your workspace ACR has been retrieved for your convenience, but you can find it yourself by looking at the printed out image location which is in the format: `<acr_name>.azurecr.io/azureml/azureml_a1234567@sha256:<image_name>`. \n",
    "\n",
    "The name of the image is the string that you'll find in `<image_name>`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "webapp_name = \"im-classif-resnet18-webapp\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You will see the JSON output of the created web app once the creation is successful."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"availabilityState\": \"Normal\",\n",
      "  \"clientAffinityEnabled\": true,\n",
      "  \"clientCertEnabled\": false,\n",
      "  \"clientCertExclusionPaths\": null,\n",
      "  \"cloningInfo\": null,\n",
      "  \"containerSize\": 0,\n",
      "  \"dailyMemoryTimeQuota\": 0,\n",
      "  \"defaultHostName\": \"im-classif-resnet18-webapp.azurewebsites.net\",\n",
      "  \"enabled\": true,\n",
      "  \"enabledHostNames\": [\n",
      "    \"im-classif-resnet18-webapp.azurewebsites.net\",\n",
      "    \"im-classif-resnet18-webapp.scm.azurewebsites.net\"\n",
      "  ],\n",
      "  \"ftpPublishingUrl\": \"ftp://waws-prod-dm1-171.ftp.azurewebsites.windows.net/site/wwwroot\",\n",
      "  \"geoDistributions\": null,\n",
      "  \"hostNameSslStates\": [\n",
      "    {\n",
      "      \"hostType\": \"Standard\",\n",
      "      \"ipBasedSslResult\": null,\n",
      "      \"ipBasedSslState\": \"NotConfigured\",\n",
      "      \"name\": \"im-classif-resnet18-webapp.azurewebsites.net\",\n",
      "      \"sslState\": \"Disabled\",\n",
      "      \"thumbprint\": null,\n",
      "      \"toUpdate\": null,\n",
      "      \"toUpdateIpBasedSsl\": null,\n",
      "      \"virtualIp\": null\n",
      "    },\n",
      "    {\n",
      "      \"hostType\": \"Repository\",\n",
      "      \"ipBasedSslResult\": null,\n",
      "      \"ipBasedSslState\": \"NotConfigured\",\n",
      "      \"name\": \"im-classif-resnet18-webapp.scm.azurewebsites.net\",\n",
      "      \"sslState\": \"Disabled\",\n",
      "      \"thumbprint\": null,\n",
      "      \"toUpdate\": null,\n",
      "      \"toUpdateIpBasedSsl\": null,\n",
      "      \"virtualIp\": null\n",
      "    }\n",
      "  ],\n",
      "  \"hostNames\": [\n",
      "    \"im-classif-resnet18-webapp.azurewebsites.net\"\n",
      "  ],\n",
      "  \"hostNamesDisabled\": false,\n",
      "  \"hostingEnvironmentProfile\": null,\n",
      "  \"httpsOnly\": false,\n",
      "  \"hyperV\": false,\n",
      "  \"id\": \"/subscriptions/0ca618d2-22a8-413a-96d0-0f1b531129c3/resourceGroups/cvbp_project_resources/providers/Microsoft.Web/sites/im-classif-resnet18-webapp\",\n",
      "  \"identity\": null,\n",
      "  \"inProgressOperationId\": null,\n",
      "  \"isDefaultContainer\": null,\n",
      "  \"isXenon\": false,\n",
      "  \"kind\": \"app,linux,container\",\n",
      "  \"lastModifiedTimeUtc\": \"2020-04-10T11:53:13.083333\",\n",
      "  \"location\": \"Central US\",\n",
      "  \"maxNumberOfWorkers\": null,\n",
      "  \"name\": \"im-classif-resnet18-webapp\",\n",
      "  \"outboundIpAddresses\": \"23.100.80.194,168.61.160.245,23.101.127.123,23.100.85.210,23.100.85.223\",\n",
      "  \"possibleOutboundIpAddresses\": \"13.89.172.22,40.77.63.65,104.43.162.5,40.77.61.181,23.99.229.97,23.100.80.194,168.61.160.245,23.101.127.123,23.100.85.210,23.100.85.223\",\n",
      "  \"redundancyMode\": \"None\",\n",
      "  \"repositorySiteName\": \"im-classif-resnet18-webapp\",\n",
      "  \"reserved\": true,\n",
      "  \"resourceGroup\": \"cvbp_project_resources\",\n",
      "  \"scmSiteAlsoStopped\": false,\n",
      "  \"serverFarmId\": \"/subscriptions/0ca618d2-22a8-413a-96d0-0f1b531129c3/resourceGroups/cvbp_project_resources/providers/Microsoft.Web/serverfarms/im_classif_webapp_plan\",\n",
      "  \"siteConfig\": null,\n",
      "  \"slotSwapStatus\": null,\n",
      "  \"state\": \"Running\",\n",
      "  \"suspendedTill\": null,\n",
      "  \"tags\": null,\n",
      "  \"targetSwapSlot\": null,\n",
      "  \"trafficManagerHostNames\": null,\n",
      "  \"type\": \"Microsoft.Web/sites\",\n",
      "  \"usageState\": \"Normal\"\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "!az webapp create --resource-group {resource_group} --plan {appservice_plan_name} --name {webapp_name} --deployment-container-image-name {image_location}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With the above command, your webapp has been created! You will be able to inspect the printed JSON output to learn more details about the created webapp.\n",
    "\n",
    "However, since you haven't provided the credentials to the Azure Container Registry where the docker image that you'll be using is hosted, the web app is not functional as it does not have access to the ACR and the image in the registry."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.C. Retrieve credentials for your Azure Container Registry (ACR) registered to the AzureML workspace<a id=\"acr_cred\" />\n",
    "\n",
    "Now that we are logged into the Azure CLI and we know the name of the ACR that our deployment image is uploaded to, let's retrieve the credentials for the ACR using Azure CLI commands.\n",
    "\n",
    "NOTE: The name of your workspace ACR has been retrieved for your convenience, but you can find it yourself by looking at the printed out image location which is in the format: `<acr_name>.azurecr.io/azureml/azureml_a1234567@sha256:<image_name>`. \n",
    "\n",
    "The name of the ACR is the string that you'll find in `<acr_name>`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "acr_name: cvwsbbd59dea\n"
     ]
    }
   ],
   "source": [
    "acr_name = image_location.split(\".\")[0]\n",
    "print(\"acr_name: {}\".format(acr_name))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "!az acr credential show --name {acr_name}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "acr_username = \"YOUR-ACR-USERNAME\"\n",
    "acr_password = \"YOUR-ACR-PASSWORD\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.D. Webapp authentication for the workspace ACR<a id=\"acr_auth\" />\n",
    "\n",
    "We will now provide the credentials that we retrieved to the web app to authenticate it for the workspace ACR.\n",
    "\n",
    "This will allow the web app to pull and load the docker image from the workspace ACR to host the scoring REST API."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "acr_server_url: cvwsbbd59dea.azurecr.io\n"
     ]
    }
   ],
   "source": [
    "acr_server_url = image_location.split(\"/\")[0]\n",
    "print(\"acr_server_url: {}\".format(acr_server_url))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You will see the JSON output of the provided container setting once the authentication is successful."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[\n",
      "  {\n",
      "    \"name\": \"WEBSITES_ENABLE_APP_SERVICE_STORAGE\",\n",
      "    \"slotSetting\": false,\n",
      "    \"value\": \"false\"\n",
      "  },\n",
      "  {\n",
      "    \"name\": \"DOCKER_REGISTRY_SERVER_URL\",\n",
      "    \"slotSetting\": false,\n",
      "    \"value\": \"cvwsbbd59dea.azurecr.io\"\n",
      "  },\n",
      "  {\n",
      "    \"name\": \"DOCKER_REGISTRY_SERVER_USERNAME\",\n",
      "    \"slotSetting\": false,\n",
      "    \"value\": \"cvwsbbd59dea\"\n",
      "  },\n",
      "  {\n",
      "    \"name\": \"DOCKER_REGISTRY_SERVER_PASSWORD\",\n",
      "    \"slotSetting\": false,\n",
      "    \"value\": null\n",
      "  },\n",
      "  {\n",
      "    \"name\": \"DOCKER_CUSTOM_IMAGE_NAME\",\n",
      "    \"value\": \"DOCKER|cvwsbbd59dea.azurecr.io/azureml/azureml_84830b7adc0a892fa927d004f3b1fcb2@sha256:4e3b949aea1fd9177ea5f53ecf1a766c9ba5c61e912c4c00b17eb9376ac26708\"\n",
      "  }\n",
      "]\n"
     ]
    }
   ],
   "source": [
    "!az webapp config container set --resource-group {resource_group} --name {webapp_name} --docker-custom-image-name {image_location} --docker-registry-server-url {acr_server_url} --docker-registry-server-user {acr_username} --docker-registry-server-password {acr_password}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. Monitor the progress of pulling and loading the deployment image<a id=\"monitor\" />\n",
    "\n",
    "Now that the web app has access to the ACR hosting the docker image, it will start pulling and loading the docker image onto the web app.\n",
    "\n",
    "We can monitor the progress by viewing the web app's logs. Let's first turn on container logging to access the console logs generated from inside the container."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"applicationLogs\": {\n",
      "    \"azureBlobStorage\": {\n",
      "      \"level\": \"Off\",\n",
      "      \"retentionInDays\": null,\n",
      "      \"sasUrl\": null\n",
      "    },\n",
      "    \"azureTableStorage\": {\n",
      "      \"level\": \"Off\",\n",
      "      \"sasUrl\": null\n",
      "    },\n",
      "    \"fileSystem\": {\n",
      "      \"level\": \"Off\"\n",
      "    }\n",
      "  },\n",
      "  \"detailedErrorMessages\": {\n",
      "    \"enabled\": false\n",
      "  },\n",
      "  \"failedRequestsTracing\": {\n",
      "    \"enabled\": false\n",
      "  },\n",
      "  \"httpLogs\": {\n",
      "    \"azureBlobStorage\": {\n",
      "      \"enabled\": false,\n",
      "      \"retentionInDays\": 3,\n",
      "      \"sasUrl\": null\n",
      "    },\n",
      "    \"fileSystem\": {\n",
      "      \"enabled\": true,\n",
      "      \"retentionInDays\": 3,\n",
      "      \"retentionInMb\": 100\n",
      "    }\n",
      "  },\n",
      "  \"id\": \"/subscriptions/0ca618d2-22a8-413a-96d0-0f1b531129c3/resourceGroups/cvbp_project_resources/providers/Microsoft.Web/sites/im-classif-resnet18-webapp/config/logs\",\n",
      "  \"kind\": null,\n",
      "  \"location\": \"Central US\",\n",
      "  \"name\": \"logs\",\n",
      "  \"resourceGroup\": \"cvbp_project_resources\",\n",
      "  \"type\": \"Microsoft.Web/sites/config\"\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "!az webapp log config --resource-group {resource_group} --name {webapp_name} --docker-container-logging filesystem"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's also make sure webserver logging is set to `filesystem` to ensure that we also save the logs from the webserver to the filesystem."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"applicationLogs\": {\n",
      "    \"azureBlobStorage\": {\n",
      "      \"level\": \"Off\",\n",
      "      \"retentionInDays\": null,\n",
      "      \"sasUrl\": null\n",
      "    },\n",
      "    \"azureTableStorage\": {\n",
      "      \"level\": \"Off\",\n",
      "      \"sasUrl\": null\n",
      "    },\n",
      "    \"fileSystem\": {\n",
      "      \"level\": \"Off\"\n",
      "    }\n",
      "  },\n",
      "  \"detailedErrorMessages\": {\n",
      "    \"enabled\": false\n",
      "  },\n",
      "  \"failedRequestsTracing\": {\n",
      "    \"enabled\": false\n",
      "  },\n",
      "  \"httpLogs\": {\n",
      "    \"azureBlobStorage\": {\n",
      "      \"enabled\": false,\n",
      "      \"retentionInDays\": 3,\n",
      "      \"sasUrl\": null\n",
      "    },\n",
      "    \"fileSystem\": {\n",
      "      \"enabled\": true,\n",
      "      \"retentionInDays\": 3,\n",
      "      \"retentionInMb\": 100\n",
      "    }\n",
      "  },\n",
      "  \"id\": \"/subscriptions/0ca618d2-22a8-413a-96d0-0f1b531129c3/resourceGroups/cvbp_project_resources/providers/Microsoft.Web/sites/im-classif-resnet18-webapp/config/logs\",\n",
      "  \"kind\": null,\n",
      "  \"location\": \"Central US\",\n",
      "  \"name\": \"logs\",\n",
      "  \"resourceGroup\": \"cvbp_project_resources\",\n",
      "  \"type\": \"Microsoft.Web/sites/config\"\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "!az webapp log config --resource-group {resource_group} --name {webapp_name} --web-server-logging filesystem"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7.A. Monitoring using the Azure Portal<a id=\"azure_portal\" />\n",
    "\n",
    "You can view the status of your webapp from the Azure Portal: [https://portal.azure.com](https://portal.azure.com)\n",
    "\n",
    "On the Azure Portal, search for the webapp that you just created using its name `im-classif-resnet18-webapp`.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/microsoft/computervision-recipes/staging/scenarios/classification/media/deployment/azure_portal.PNG\" />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's try taking a look at the status of the web app from the portal and make sure that the webapp is up and running.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/microsoft/computervision-recipes/staging/scenarios/classification/media/deployment/webapp_status.PNG\" />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we have made sure that the web app is running, let's take a look at the logs by going to the `Container settings` section.\n",
    "\n",
    "Set the `Image source` as `Private Registry` and make sure the details of the registry and image match the values specified in the above steps.\n",
    "\n",
    "Once the image has been loaded and the web app is fully functional, the log will display a message stating:\n",
    "\n",
    "`Container <image_location> for site <webapp_name> initialized successfully and is ready to serve requests.`\n",
    "\n",
    "NOTE: Due to the size of the image it may take between 10-15 minutes for the pulling, extracting and loading of the docker image to complete. Also, the logs do not auto refresh, so you will need to manually refresh to pull the latest logs.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/microsoft/computervision-recipes/staging/scenarios/classification/media/deployment/webapp_logs.PNG\" />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7.B. Monitoring using streamed logs via Azure CLI<a id=\"azure_cli\" />\n",
    "\n",
    "We can also stream the logs from the webapp using the Azure CLI commands. This allows us to more easily view the webapp's ongoing processes without having to manually refresh the logs. This is the easier and preferred way to monitor the deployment process.\n",
    "\n",
    "Run the command below from a shell program:\n",
    "\n",
    "`az webapp log tail --resource-group <YOUR-RESOURCE-GROUP> --name <YOUR-WEBAPP-NAME>`\n",
    "\n",
    "NOTE: Running the above command in a notebook using `!` will not work as running bash shells in jupyter notebooks will not display any output until everything is finished running.\n",
    "\n",
    "Once you see in the logs the following lines indicating that the deployment was successful, you can stop/kill the process as it will continue to listen for new logs from the deployed service. Stopping the process WILL NOT interrupt the service or cause it to stop, so there's no need to worry as it will only stop listening for additional logs.\n",
    "\n",
    "`2020-04-10 12:08:02.010 INFO  - Container im-classif-resnet18-webapp_0_ef19ced8 for site im-classif-resnet18-webapp initialized successfully and is ready to serve requests.`\n",
    "\n",
    "NOTE: If you run into issues in log streaming, fall back to [monitoring using the Azure Portal](#azure_portal) which may provide additional details."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Test the scoring REST API endpoint for the deployed web app<a id=\"test\" />\n",
    "\n",
    "We will now test the scoring endpoint for the deployed web app using the test images hosted on Azure Blob Storage and then pre-process them using the utility function `ims2strlist()` into string decoded base64 encoded image bytes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_image_directory = \"https://cvbp-secondary.z19.web.core.windows.net/images/\"\n",
    "test_image_filenames = [\"cvbp_milk_bottle.jpg\", \"cvbp_water_bottle.jpg\"]\n",
    "local_test_image_paths = []\n",
    "\n",
    "for test_image_filename in test_image_filenames:\n",
    "    req = requests.get(os.path.join(test_image_directory, test_image_filename))\n",
    "    local_test_image_path = os.path.join(data_path(), test_image_filename)\n",
    "\n",
    "    with open(local_test_image_path, \"wb\") as file:\n",
    "        file.write(req.content)\n",
    "        local_test_image_paths.append(local_test_image_path)\n",
    "\n",
    "# Use the utility function im2strlist to get a list containing base64-encoded images decoded into strings\n",
    "decoded_b64_test_images = ims2strlist(local_test_image_paths)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With the two test images that we downloaded in the step above, let's test the scoring endpoint using the `requests` library.\n",
    "\n",
    "You should get the predictions back in the format of a JSON string, containing the labels and their probabilities for the test images.\n",
    "\n",
    "If the labels for the two test images came back as `water_bottle`, then congratulations, your scoring endpoint using the pretrained machine learning model is now fully up and running!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predictions: [{'label': 'water_bottle', 'probability': '0.800184428691864'}, {'label': 'water_bottle', 'probability': '0.6857761144638062'}]\n",
      "Received the scored result in: 0:00:01.063833\n",
      "Response status code: 200\n"
     ]
    }
   ],
   "source": [
    "scoring_uri = \"https://{}.azurewebsites.net/score\".format(webapp_name)\n",
    "\n",
    "headers = {\"Content-Type\" : \"application/json\"}\n",
    "\n",
    "test_image_data = json.dumps({\"data\" : decoded_b64_test_images})\n",
    "\n",
    "response = requests.post(scoring_uri, data=test_image_data, headers=headers)\n",
    "\n",
    "print(\"Predictions: {}\".format(response.json()),\n",
    "      \"Received the scored result in: {}\".format(response.elapsed),\n",
    "      \"Response status code: {}\".format(response.status_code), sep=\"\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. Cross-Origin Resource Sharing (CORS)<a id=\"cors\"/>\n",
    "\n",
    "Cross-Origin Resource Sharing (CORS) is an HTTP feature that allows a web application running under a single domain to access resources from another domain. \n",
    "\n",
    "By default, web browsers enable [same origin policy](https://www.w3.org/Security/wiki/Same_Origin_Policy), a security mechanism to prevent webpages from calling APIs from a different domain than the one it is calling from. Under the same origin policy, one origin is allowed to send information to another origin, but is not permitted to receive information from another origin to prevent malicious websites from accessing confidential information from other websites.\n",
    "\n",
    "CORS provides a secure way to allow one origin to both call and receive information from APIs that are hosted in a different origin.\n",
    "\n",
    "**Hence, setting the correct CORS policy is required if you are building a web application that is hosted in a different domain from the one hosting the deployed machine learning model's RESTful APIs.**\n",
    "\n",
    "To learn more about CORS in detail, please refer to either the [CORS specification from World Wide Web Consortium (W3C)](https://www.w3.org/TR/cors/) or [Mozilla's documentation page for developers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Allowed origins: \"['YOUR-WEBAPP-ORIGIN']\"\n"
     ]
    }
   ],
   "source": [
    "# To allow multiple origins, add them to the allowed origins list. To allow all client URLs, add the wildcard '*'\n",
    "origins = \"\\\"['YOUR-WEBAPP-ORIGIN']\\\"\" # The string format for allowed origins HAS to be in the format \"['abc']\" \n",
    "print(\"Allowed origins: {}\".format(origins))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Make sure you see the allowedOrigins configuration correctly set in `[\"properties\"][\"cors\"][\"allowedOrigins\"]`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"id\": \"/subscriptions/0ca618d2-22a8-413a-96d0-0f1b531129c3/resourceGroups/cvbp_project_resources/providers/Microsoft.Web/sites/im-classif-resnet18-webapp\",\n",
      "  \"identity\": null,\n",
      "  \"kind\": null,\n",
      "  \"location\": \"Central US\",\n",
      "  \"managedBy\": null,\n",
      "  \"name\": \"im-classif-resnet18-webapp\",\n",
      "  \"plan\": null,\n",
      "  \"properties\": {\n",
      "    \"alwaysOn\": true,\n",
      "    \"apiDefinition\": null,\n",
      "    \"apiManagementConfig\": null,\n",
      "    \"appCommandLine\": \"\",\n",
      "    \"appSettings\": null,\n",
      "    \"autoHealEnabled\": false,\n",
      "    \"autoHealRules\": null,\n",
      "    \"autoSwapSlotName\": null,\n",
      "    \"azureMonitorLogCategories\": null,\n",
      "    \"connectionStrings\": null,\n",
      "    \"cors\": {\n",
      "      \"allowedOrigins\": [\n",
      "        \"YOUR-WEBAPP-ORIGIN\"\n",
      "      ],\n",
      "      \"supportCredentials\": false\n",
      "    },\n",
      "    \"customAppPoolIdentityAdminState\": false,\n",
      "    \"customAppPoolIdentityTenantState\": false,\n",
      "    \"defaultDocuments\": [\n",
      "      \"Default.htm\",\n",
      "      \"Default.html\",\n",
      "      \"Default.asp\",\n",
      "      \"index.htm\",\n",
      "      \"index.html\",\n",
      "      \"iisstart.htm\",\n",
      "      \"default.aspx\",\n",
      "      \"index.php\",\n",
      "      \"hostingstart.html\"\n",
      "    ],\n",
      "    \"detailedErrorLoggingEnabled\": false,\n",
      "    \"documentRoot\": null,\n",
      "    \"experiments\": {\n",
      "      \"rampUpRules\": []\n",
      "    },\n",
      "    \"fileChangeAuditEnabled\": false,\n",
      "    \"ftpsState\": \"AllAllowed\",\n",
      "    \"functionsRuntimeScaleMonitoringEnabled\": false,\n",
      "    \"handlerMappings\": null,\n",
      "    \"healthCheckPath\": null,\n",
      "    \"http20Enabled\": true,\n",
      "    \"httpLoggingEnabled\": true,\n",
      "    \"ipSecurityRestrictions\": null,\n",
      "    \"javaContainer\": null,\n",
      "    \"javaContainerVersion\": null,\n",
      "    \"javaVersion\": null,\n",
      "    \"limits\": null,\n",
      "    \"linuxFxVersion\": \"DOCKER|cvwsbbd59dea.azurecr.io/azureml/azureml_84830b7adc0a892fa927d004f3b1fcb2@sha256:4e3b949aea1fd9177ea5f53ecf1a766c9ba5c61e912c4c00b17eb9376ac26708\",\n",
      "    \"loadBalancing\": 1,\n",
      "    \"localMySqlEnabled\": false,\n",
      "    \"logsDirectorySizeLimit\": 100,\n",
      "    \"machineKey\": null,\n",
      "    \"managedPipelineMode\": 0,\n",
      "    \"managedServiceIdentityId\": null,\n",
      "    \"metadata\": null,\n",
      "    \"minTlsVersion\": \"1.2\",\n",
      "    \"netFrameworkVersion\": \"v4.0\",\n",
      "    \"nodeVersion\": \"\",\n",
      "    \"numberOfWorkers\": 1,\n",
      "    \"phpVersion\": \"\",\n",
      "    \"preWarmedInstanceCount\": null,\n",
      "    \"publishingPassword\": null,\n",
      "    \"publishingUsername\": \"$im-classif-resnet18-webapp\",\n",
      "    \"push\": null,\n",
      "    \"pythonVersion\": \"\",\n",
      "    \"remoteDebuggingEnabled\": false,\n",
      "    \"remoteDebuggingVersion\": \"VS2019\",\n",
      "    \"requestTracingEnabled\": false,\n",
      "    \"reservedInstanceCount\": 0,\n",
      "    \"routingRules\": [],\n",
      "    \"runtimeADUser\": null,\n",
      "    \"runtimeADUserPassword\": null,\n",
      "    \"scmIpSecurityRestrictions\": null,\n",
      "    \"scmIpSecurityRestrictionsUseMain\": null,\n",
      "    \"scmType\": \"None\",\n",
      "    \"siteAuthEnabled\": false,\n",
      "    \"siteAuthSettings\": {\n",
      "      \"additionalLoginParams\": null,\n",
      "      \"allowedAudiences\": null,\n",
      "      \"allowedExternalRedirectUrls\": null,\n",
      "      \"clientId\": null,\n",
      "      \"clientSecret\": null,\n",
      "      \"clientSecretCertificateThumbprint\": null,\n",
      "      \"defaultProvider\": null,\n",
      "      \"enabled\": null,\n",
      "      \"facebookAppId\": null,\n",
      "      \"facebookAppSecret\": null,\n",
      "      \"facebookOAuthScopes\": null,\n",
      "      \"googleClientId\": null,\n",
      "      \"googleClientSecret\": null,\n",
      "      \"googleOAuthScopes\": null,\n",
      "      \"isAadAutoProvisioned\": false,\n",
      "      \"issuer\": null,\n",
      "      \"microsoftAccountClientId\": null,\n",
      "      \"microsoftAccountClientSecret\": null,\n",
      "      \"microsoftAccountOAuthScopes\": null,\n",
      "      \"tokenStoreEnabled\": null,\n",
      "      \"twitterConsumerKey\": null,\n",
      "      \"twitterConsumerSecret\": null,\n",
      "      \"unauthenticatedClientAction\": null\n",
      "    },\n",
      "    \"tracingOptions\": null,\n",
      "    \"use32BitWorkerProcess\": true,\n",
      "    \"virtualApplications\": [\n",
      "      {\n",
      "        \"physicalPath\": \"site\\\\wwwroot\",\n",
      "        \"preloadEnabled\": true,\n",
      "        \"virtualDirectories\": null,\n",
      "        \"virtualPath\": \"/\"\n",
      "      }\n",
      "    ],\n",
      "    \"vnetName\": \"\",\n",
      "    \"webSocketsEnabled\": false,\n",
      "    \"websiteTimeZone\": null,\n",
      "    \"winAuthAdminState\": 0,\n",
      "    \"winAuthTenantState\": 0,\n",
      "    \"windowsFxVersion\": null,\n",
      "    \"xManagedServiceIdentityId\": null\n",
      "  },\n",
      "  \"resourceGroup\": \"cvbp_project_resources\",\n",
      "  \"sku\": null,\n",
      "  \"tags\": null,\n",
      "  \"type\": \"Microsoft.Web/sites\"\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "!az resource update --resource-group {resource_group} --name web --namespace Microsoft.Web --resource-type config --parent sites/{webapp_name} --set properties.cors.allowedOrigins={origins} --api-version 2015-06-01"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 10. Clean up<a id=\"clean_up\" />\n",
    "\n",
    "If you would like to delete the webapp and the appservice plan, uncomment the two cells below and run the commands."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#!az webapp delete --resource-group {resource_group} --name \"im-classif-resnet18-webapp\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#!az appservice plan delete --resource-group {resource_group} --name {appservice_plan_name} --yes"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
